/*
The MIT License (MIT)

Copyright (c) 2013 Mike Dapiran, Brian May, Richard Pospesel, and Bert Wierenga

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include "DirectInputManager.h"

#include <Windows.h>

#pragma comment(lib,"dinput8.lib")
#pragma comment(lib,"dxguid.lib")

namespace hhd
{
    HWND DirectInputManager::_window_handle = NULL;

    LPDIRECTINPUT8 DirectInputManager::_di_object = NULL;
    LPDIRECTINPUTDEVICE8 DirectInputManager::_keyboard_device = NULL;
    LPDIRECTINPUTDEVICE8 DirectInputManager::_mouse_device = NULL;

    char DirectInputManager::_keyboard_buffer[256];
    Input::KeyState* DirectInputManager::_ks_0 = NULL;
    Input::KeyState* DirectInputManager::_ks_1 = NULL;

    DIMOUSESTATE2 DirectInputManager::_mouse_buffer;
    int* DirectInputManager::_mouse_deltas = NULL;
    int* DirectInputManager::_mouse_positions = NULL;
    Input::ButtonState* DirectInputManager::_mouse_button_states_0 = NULL;
    Input::ButtonState* DirectInputManager::_mouse_button_states_1 = NULL;

    bool DirectInputManager::_mouse_moved = false;
    bool DirectInputManager::_mouse_buttons_changed = false;


    void DirectInputManager::initialize()
    {
        _window_handle =  GetActiveWindow();

        // get direct input object
        DirectInput8Create(GetModuleHandle(NULL), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&_di_object, NULL);

        // get keyboard object
        _di_object->CreateDevice(GUID_SysKeyboard, &_keyboard_device, NULL);
        _keyboard_device->SetDataFormat( &c_dfDIKeyboard );
        _keyboard_device->SetCooperativeLevel(_window_handle, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);

        // set keyboard by default to all keys up and unchanged
        _ks_0 = new Input::KeyState[256];
        memset(_ks_0, Input::Released | Input::Unchanged, 256);
        _ks_1 = new Input::KeyState[256];
        memset(_ks_1, Input::Released | Input::Unchanged, 256);

        // get mouse object
        _di_object->CreateDevice(GUID_SysMouse, &_mouse_device, NULL);
        _mouse_device->SetDataFormat( &c_dfDIMouse2 );
        _mouse_device->SetCooperativeLevel(_window_handle, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);

        // initialize mouse memory
        _mouse_deltas = new int[2];
        _mouse_positions = new int[2];
        _mouse_button_states_0 = new Input::ButtonState[8];
        memset(_mouse_button_states_0, Input::Pressed | Input::Unchanged, 8);
        _mouse_button_states_1 = new Input::ButtonState[8];
        memset(_mouse_button_states_1, Input::Pressed | Input::Unchanged, 8);
    }

    void DirectInputManager::finalize()
    {
        delete[] _ks_0;
        delete[] _ks_1;

        delete[] _mouse_deltas;
        delete[] _mouse_positions;

        delete[] _mouse_button_states_0;
        delete[] _mouse_button_states_1;
    }

#pragma warning( push )
    // so it doesn't yell about KeyState::blah
#pragma warning(disable : 4482)

    bool DirectInputManager::pollKeyboard()
    {
        // first zero out buffer
        memset(_keyboard_buffer, 0, sizeof(_keyboard_buffer));
        // fill state
        HRESULT hr = _keyboard_device->GetDeviceState(sizeof(_keyboard_buffer), _keyboard_buffer);
        if(FAILED(hr))
        {
            do
            {
                hr = _keyboard_device->Acquire();
            }
            while(hr == DIERR_INPUTLOST);

            if(FAILED(hr))
            {
                return false;
            }

        }

        // swap our keyboard buffers
        Input::KeyState* temp = _ks_0;
        _ks_0 = _ks_1;
        _ks_1 = temp;

        bool result = false;

        for(int k = 0; k < sizeof(_keyboard_buffer); k++)
        {
            if(_keyboard_buffer[k] & 0x80)
            {
                _ks_0[k] = Input::Pressed;
            }
            else
            {
                _ks_0[k] = Input::Released;
            }

            // you bit twiddler me
            if(_ks_0[k] == (_ks_1[k] & (Input::Released | Input::Pressed)))	// strip out change bits and just compare key's location state
            {
                _ks_0[k] = (Input::KeyState)(_ks_0[k] | Input::Unchanged);
            }
            else
            {
                _ks_0[k] = (Input::KeyState)(_ks_0[k] | Input::Changed);
                result = true;
            }
        }

        return result;
    }

#pragma warning( pop )

    bool DirectInputManager::pollMouse(bool confine_to_window)
    {

        if(confine_to_window)
        {
            RECT win_r;
            RECT client_r;
            GetWindowRect(_window_handle, &win_r);
            GetClientRect(_window_handle, &client_r);

            int client_width = (client_r.right - client_r.left) - 1;
            int window_width = (win_r.right - win_r.left) - 1;

            int border_width = (window_width - client_width) / 2;

            RECT r;
            r.left = win_r.left + border_width;
            r.right = win_r.right - border_width;
            r.bottom = win_r.bottom - border_width;
            int client_height = client_r.bottom - client_r.top;
            r.top = r.bottom - client_height;


            ClipCursor(&r);
        }
        else
        {
            ClipCursor(NULL);
        }


        _mouse_moved = false;
        _mouse_buttons_changed = false;
        // first zero out buffer
        memset(&_mouse_buffer, 0, sizeof(_mouse_buffer));

        HRESULT hr = _mouse_device->GetDeviceState(sizeof(_mouse_buffer), &_mouse_buffer);
        if(FAILED(hr))
        {
            do
            {
                hr = _mouse_device->Acquire();
            }
            while(hr == DIERR_INPUTLOST);

            if(FAILED(hr))
            {
                return false;
            }
        }

        // swap our mouse state buffers

        Input::ButtonState* temp1 = _mouse_button_states_0;
        _mouse_button_states_0 = _mouse_button_states_1;
        _mouse_button_states_1 = temp1;

        //copy locationdata into new buffer
        _mouse_deltas[0] = _mouse_buffer.lX;
        _mouse_deltas[1] = _mouse_buffer.lY;
        //_mouse_buffer.lZ would give us scroll wheel info

        // now see if there is a difference

        // first check mouse deltas
        if(_mouse_deltas[0] != 0 || _mouse_deltas[1] != 0)
        {
            _mouse_moved = true;
        }

        // now see if the mouse button state has changed
        for(int k = 0; k < 8; k++)
        {
            if(_mouse_buffer.rgbButtons[k] & 0x80)
            {
                _mouse_button_states_0[k] = Input::Pressed;
            }
            else
            {
                _mouse_button_states_0[k] = Input::Released;
            }

            if(_mouse_button_states_0[k] == (_mouse_button_states_1[k] & (Input::Pressed | Input::Released)))
            {
                _mouse_button_states_0[k] = (Input::ButtonState)(_mouse_button_states_0[k] | Input::Unchanged);
            }
            else
            {
                _mouse_button_states_0[k] = (Input::ButtonState)(_mouse_button_states_0[k] | Input::Changed);
                _mouse_buttons_changed = true;
            }
        }
        POINT p;
        if(GetCursorPos(&p) != 0)	// get absolute  mouse position
        {
            if(ScreenToClient(_window_handle, &p) != 0) // convert to screen space
            {
                // update mouse positions if all that was good
                _mouse_positions[0] = p.x;
                _mouse_positions[1] = p.y;
            }
        }

        return _mouse_buttons_changed || _mouse_moved;
    }
}